#!/bin/bash
set -e

PROGRAM="$0"

success() {
  echo -e "\e[1;32mDone:\e[m $1"
  exit 0
}

fail() {
  echo -e "\e[1;31mError:\e[m $1"
  exit 1
}

commit() {
  local message="$1"
  git add .
  git commit -qm"${message}"
}

get_root() {
  git ls-tree HEAD:third_party | sed -n 's/^.* \([^ ]\+\)\ttock$/\1/p'
}

get_head() {
  git rev-parse HEAD
}

help() {
  local root="$(get_root)"
  cat <<EOF
Usage: ${PROGRAM} {apply|save}

  apply       Applies the patches to the Tock submodule regardless of its state.
              As a consequence this can always be called to get to a clean state
              but may result in data loss if there are unsaved changes.

  save        Saves the Tock submodule to the patches.
              This should only be called after apply and when all changes have
              been added to a commit. After saving, you can run ./setup.sh to
              return to normal state. Otherwise you can continue editing Tock
              and calling save.

  restore     Restores the Tock submodule to its normal state regardless of its
              state. As a consequence this can always be called to get to a
              clean state but may result in data loss if there are unsaved
              changes.

Example:

  1. Enter the edit state from the normal state:

    ${PROGRAM} apply

  2. Edit files in the Tock submodule:

    cd third_party/tock
    edit <files>

  3. Create a fix commit per affected patch by repeating the following commands
  until there are no more files to add:

    git add -p
    git commit -m'fix <patch#>'

  4. Merge the fixes into their patches by moving their line below their patch
  and changing their "edit" into "fixup":

    git rebase -i ${root}

  5. Save the changes:

    cd ../..
    ${PROGRAM} save

  6. Either continue repeating steps 2 to 5, or return to the normal state:

    ${PROGRAM} restore
EOF
  exit 0
}

apply() {
  local root="$(get_root)"
  ( set -e
    cd third_party/tock
    git reset -q --hard
    git clean -qfxd
    git checkout -q "${root}"
    cp -a ../../boards .
    commit '00-boards'
    for file in ../../patches/tock/*; do
      git apply "${file}"
      commit "$(basename "${file}" .patch)"
    done
  )
}

save() {
  local root="$(get_root)"
  ( set -e
    cd third_party/tock
    [[ -z "$(git status -s)" ]] || fail 'The Tock submodule is not clean.'
    rm ../../patches/tock/*.patch
    for file in $(git format-patch "${root}"); do
      sed -n '/^diff/,$p' "${file}" \
        | head -n-3 > "../../patches/tock/${file#*-}"
    done
    git clean -qfxd
    top="$(get_head)"
    git checkout -q "${root}"
    rm -r boards
    git apply --whitespace=nowarn ../../patches/tock/00-boards.patch
    rm ../../patches/tock/00-boards.patch
    rm -r ../../boards
    cp -a boards ../..
    git reset -q --hard
    git clean -qfxd
    git checkout -q "${top}"
  )
}

grep -q third_party/tock .gitmodules 2>/dev/null \
  || fail 'Not running from OpenSK directory.'
[[ $# -eq 1 ]] || help
case $1 in
  apply)
    apply
    success 'Applied the patches to the Tock submodule.'
    ;;
  save)
    save
    success 'Saved the Tock submodule to the patches.'
    ;;
  restore)
    # Ovewrite the commit function to do nothing.
    commit() { true; }
    apply
    success 'Restored the Tock submodule.'
    ;;
  *) fail 'Unexpected argument. Run without argument for help.' ;;
esac
